package com.etool.commons.rmtshell;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JOptionPane;

import org.apache.taglibs.standard.tag.common.core.OutSupport;

import com.etool.commons.rmtshell.SwingShell.AdvancedVerifier;
import com.etool.commons.rmtshell.SwingShell.EnterSomethingDialog;
import com.etool.commons.rmtshell.SwingShell.InteractiveLogic;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.InteractiveCallback;
import ch.ethz.ssh2.KnownHosts;
import ch.ethz.ssh2.ServerHostKeyVerifier;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

/**
 * 遠程shell調用
 * 
 * @author linjia
 * 
 */
public class RmtShellExecutor {

	static KnownHosts database = new KnownHosts();

	static final String knownHostPath = "~/.ssh/known_hosts";
	static final String idDSAPath = "~/.ssh/id_dsa";
	static final String idRSAPath = "~/.ssh/id_rsa";

	/**  */
	private Connection conn;

	/**
	 * 远程机器IP
	 * */
	private String ip;

	/**
	 * 端口
	 */
	private int port;

	/**
	 * 用户名
	 */
	private String usr;

	/**
	 * 密码
	 * */
	private String psword;

	private String charset = "GBK";

	private static final int TIME_OUT = 1000 * 5 * 60;

	public RmtShellExecutor(String ip, int port, String username,
			String password) {
		this.ip = ip;
		this.port = port;
		this.usr = username;
		this.psword = password;
	}

	private boolean login() throws IOException {
		
		String[] hostkeyAlgos = database.getPreferredServerHostkeyAlgorithmOrder(ip);
		
		conn = new Connection(ip, port);
		
		if (hostkeyAlgos != null)
			conn.setServerHostKeyAlgorithms(hostkeyAlgos);

		conn.connect(new AdvancedVerifier());
		
		boolean enableKeyboardInteractive = true;
		boolean enableDSA = true;
		boolean enableRSA = true;

		String lastError = null;

		while (true)
		{
			if ((enableDSA || enableRSA) && conn.isAuthMethodAvailable(usr, "publickey"))
			{
				if (enableDSA)
				{
					File key = new File(idDSAPath);

					if (key.exists())
					{
						boolean res = conn.authenticateWithPublicKey(usr, key, psword);

						if (res == true)
							break;

						lastError = "DSA authentication failed.";
					}
					enableDSA = false; // do not try again
				}

				if (enableRSA)
				{
					File key = new File(idRSAPath);

					if (key.exists())
					{
						boolean res = conn.authenticateWithPublicKey(usr, key, psword);

						if (res == true)
							break;

						lastError = "RSA authentication failed.";
					}
					enableRSA = false; // do not try again
				}

				continue;
			}

			if (enableKeyboardInteractive && conn.isAuthMethodAvailable(usr, "keyboard-interactive"))
			{
				InteractiveLogic il = new InteractiveLogic(lastError);

				boolean res = conn.authenticateWithKeyboardInteractive(usr, il);

				if (res == true)
					break;

				if (il.getPromptCount() == 0)
				{
					// aha. the server announced that it supports "keyboard-interactive", but when
					// we asked for it, it just denied the request without sending us any prompt.
					// That happens with some server versions/configurations.
					// We just disable the "keyboard-interactive" method and notify the user.

					lastError = "Keyboard-interactive does not work.";

					enableKeyboardInteractive = false; // do not try this again
				}
				else
				{
					lastError = "Keyboard-interactive auth failed."; // try again, if possible
				}

				continue;
			}

			if (conn.isAuthMethodAvailable(usr, "password"))
			{
				

				boolean res = conn.authenticateWithPassword(usr, psword);

				if (res == true)
					break;

				lastError = "Password authentication failed."; // try again, if possible

				continue;
			}

			throw new IOException("No supported authentication methods available.");
		}
	
		return true;

	}

	public Map<String, Object> exec(String cmds) {

		Map<String, Object> result = new HashMap<String, Object>();

		InputStream stdOut = null;
		InputStream stdErr = null;
		String output = null;
		String errMsg = null;
		int status = -1;

		Session session = null;
		try {
			if (login()) {
				session = conn.openSession();

				session.execCommand(cmds);

				stdOut = new StreamGobbler(session.getStdout());
				output = processStream(stdOut, charset).trim();

				stdErr = new StreamGobbler(session.getStderr());
				errMsg = processStream(stdErr, charset).trim();

				session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT);

				result.put("output", output);
				result.put("errMsg", errMsg);

				status = session.getExitStatus();
			} else {
				result.put("errMsg", "login Failed");
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (session != null) {
				session.close();
			}
			if (conn != null) {
				conn.close();
			}
			if (stdOut != null) {
				closeIOStream(stdOut);
			}
			if (stdErr != null) {
				closeIOStream(stdErr);
			}
		}

		result.put("status", status);
		return result;
	}

	private void closeIOStream(InputStream in) {
		try {
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private String processStream(InputStream in, String charset)
			throws UnsupportedEncodingException, IOException {
		byte[] buf = new byte[1024];
		StringBuilder sb = new StringBuilder();
		while (in.read(buf) != -1) {
			sb.append(new String(buf, charset));
		}
		return sb.toString();
	}

	public static void main(String[] args) throws IOException {
		RmtShellExecutor cmd = new RmtShellExecutor("10.10.11.193", 16333,
				"publish", "2Y39bVu78UK2");
		Map<String, Object> res = cmd.exec("ls -alh");
		System.out.print(res);
	}

	class AdvancedVerifier implements ServerHostKeyVerifier {
		public boolean verifyServerHostKey(String hostname, int port,
				String serverHostKeyAlgorithm, byte[] serverHostKey)
				throws Exception {
			final String host = hostname;
			final String algo = serverHostKeyAlgorithm;

			String message;

			/* Check database */

			int result = database.verifyHostkey(hostname,serverHostKeyAlgorithm, serverHostKey);

			switch (result) {
			case KnownHosts.HOSTKEY_IS_OK:
				return true;

			case KnownHosts.HOSTKEY_IS_NEW:
				message = "Do you want to accept the hostkey (type " + algo
						+ ") from " + host + " ?\n";
				break;

			case KnownHosts.HOSTKEY_HAS_CHANGED:
				message = "WARNING! Hostkey for " + host
						+ " has changed!\nAccept anyway?\n";
				break;

			default:
				throw new IllegalStateException();
			}
			
			System.out.println("==============="+message);
			/* Include the fingerprints in the message */

			String hexFingerprint = KnownHosts.createHexFingerprint(
					serverHostKeyAlgorithm, serverHostKey);
			String bubblebabbleFingerprint = KnownHosts
					.createBubblebabbleFingerprint(serverHostKeyAlgorithm,
							serverHostKey);

			message += "Hex Fingerprint: " + hexFingerprint
					+ "\nBubblebabble Fingerprint: " + bubblebabbleFingerprint;


			String hashedHostname = KnownHosts.createHashedHostname(hostname);

			database.addHostkey(new String[] { hashedHostname },
					serverHostKeyAlgorithm, serverHostKey);

			try {
				KnownHosts.addHostkeyToFile(new File(knownHostPath),
						new String[] { hashedHostname },
						serverHostKeyAlgorithm, serverHostKey);
			} catch (IOException ignore) {
			}

			return true;
		}
	}
	
	class InteractiveLogic implements InteractiveCallback
	{
		int promptCount = 0;
		String lastError;

		public InteractiveLogic(String lastError)
		{
			this.lastError = lastError;
		}

		/* the callback may be invoked several times, depending on how many questions-sets the server sends */

		public String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt,
				boolean[] echo) throws IOException
		{
			String[] result = new String[numPrompts];

			for (int i = 0; i < numPrompts; i++)
			{
				/* Often, servers just send empty strings for "name" and "instruction" */

				String[] content = new String[] { lastError, name, instruction, prompt[i] };

				if (lastError != null)
				{
					/* show lastError only once */
					lastError = null;
				}

				result[i] = psword;
				promptCount++;
			}

			return result;
		}

		/* We maintain a prompt counter - this enables the detection of situations where the ssh
		 * server is signaling "authentication failed" even though it did not send a single prompt.
		 */

		public int getPromptCount()
		{
			return promptCount;
		}
	}

}
